package com.lam.common.components.easyexcel.export;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.OutputStream;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.List;

import javax.servlet.http.HttpServletResponse;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.MediaType;

import com.alibaba.excel.EasyExcelFactory;
import com.alibaba.excel.ExcelWriter;
import com.alibaba.excel.write.builder.ExcelWriterSheetBuilder;
import com.alibaba.excel.write.handler.CellWriteHandler;
import com.alibaba.excel.write.handler.WriteHandler;
import com.alibaba.excel.write.metadata.WriteSheet;
import com.lam.common.components.easyexcel.constant.ExcelConstant;
import com.lam.common.components.easyexcel.export.iterator.DataIterator;
import com.lam.common.components.easyexcel.handlers.DefaultCellStyleWriteHandler;
import com.lam.common.components.file.FileDownloadUtil;
import com.lam.common.components.file.model.FileDownloadInfo;
import com.lam.common.components.file.model.FileBusiTypeEnum;
import com.lam.common.config.AppBaseConfig;
import com.lam.common.context.ContextHolder;
import com.lam.common.context.TokenUtils;
import com.lam.common.exception.BusinessException;
import com.lam.common.utils.CollectionUtil;
import com.lam.common.utils.DateUtil;
import com.lam.common.utils.ZipUtils;
import com.lam.common.utils.file.FileUtil;

public class DownloadUtil {

	private static final Logger logger = LoggerFactory.getLogger(DownloadUtil.class);

    private DownloadUtil() {
        throw new IllegalStateException("Utility  class");
    }

    public static <T> void createExcel(String sheetName, List<T> list, Class<T> clazz, OutputStream outputStream, WriteHandler... handlers) {

        ExcelWriterSheetBuilder writerSheetBuilder = EasyExcelFactory.write(outputStream, clazz).sheet(sheetName);
        boolean hasCellWriteHandler = false;
        if (CollectionUtil.isNotEmpty(handlers)) {
            for (WriteHandler writeHandler : handlers) {
            	if(writeHandler == null) {
            		continue;
            	}
            	if(writeHandler instanceof CellWriteHandler) {
            		hasCellWriteHandler = true;
            	}
                writerSheetBuilder.registerWriteHandler(writeHandler);
            }
            
        }
    	if(!hasCellWriteHandler) {
    		writerSheetBuilder.registerWriteHandler(new DefaultCellStyleWriteHandler());
        }
        writerSheetBuilder.doWrite(list);
    }
    
    public static <T> void downloadExcel(String fileName, String sheetName, List<T> list, Class<T> clazz, WriteHandler... handlers) {
        downloadFile(fileName, out -> createExcel(sheetName, list, clazz, out, handlers));
    }
    
    public static WriteHandler[] getDefaultHandlers(WriteHandler... handlers) {
    	if(CollectionUtil.isEmpty(handlers)) {
    		return new WriteHandler[] {new DefaultCellStyleWriteHandler()};
    	}
    	WriteHandler[] newHandlers = new WriteHandler[handlers.length+1];
    	newHandlers[0] = new DefaultCellStyleWriteHandler();
    	int i = 1;
    	for (WriteHandler handler : handlers) {
    		newHandlers[i++] = handler;
		}
    	return newHandlers;
    }

    public static void downloadFile(String fileName, DownloadWriterBody writerBody) {
        HttpServletResponse response = ContextHolder.getResponse();
        try {
            response.setCharacterEncoding("utf-8");
            response.setContentType(MediaType.APPLICATION_OCTET_STREAM.getSubtype());
            // 这里注意 有同学反应使用swagger 会导致各种问题，请直接用浏览器或者用postman
//	        response.setContentType("application/vnd.ms-excel")

            // 这里URLEncoder.encode可以防止中文乱码
            response.setHeader("Content-disposition", "attachment;filename=" + URLEncoder.encode(fileName, "UTF-8"));
            writerBody.exe(response.getOutputStream());
        } catch (Exception e) {
            FileDownloadUtil.writeFail(response, e, fileName);
        }
    }
    
    public static <T> FileDownloadInfo exportExcel(String title, List<T> list, Class<T> clazz, WriteHandler ... handlers) {
    	return exportExcel(String.format("%s_%s.xlsx", title, System.currentTimeMillis()), title, list, clazz, handlers);
    }
    
    public static String getDefaultExportDir() {
    	String userid = TokenUtils.getLoginUserid();
    	return AppBaseConfig.me().getDownloadTmpPath() + userid + ExcelConstant.EXPORT_DIR;
    }

    public static <T> FileDownloadInfo exportExcel(String fileName, String sheetName, List<T> list, Class<T> clazz, WriteHandler ... handlers) {
		
    	if(!fileName.endsWith(".xlsx") && !fileName.endsWith(".xls")) {
    		fileName = String.format("%s导出_%s.xlsx", fileName, System.currentTimeMillis());
    	}
    	
		String parent = getDefaultExportDir();
		if(!FileUtil.mkdir(parent)) {
			throw new BusinessException("导出Excel异常");
		}
		String path = parent + fileName;
		try (OutputStream outputStream = new FileOutputStream(path)){
	        createExcel(sheetName, list, clazz, outputStream, handlers);
	        return FileDownloadUtil.createDownloadInfo(FileBusiTypeEnum.DOWNLOAD_TMP, path);
		} catch (Exception e) {
			logger.error(e.getMessage(), e);
			throw new BusinessException("导出Excel异常");
		}
	}
	
    public static interface DownloadWriterBody {
        void exe(OutputStream outputStream);
    }

    /**
     * 分批导出Excel
     * @param <T>
     * @param title Excel名称
     * @param dir 目录名称
     * @param dataIterator 数据迭代器
     * @param clazz 数据类
     */
    public static <T> void exportDataByBatch(String title, String dir, DataIterator<T> dataIterator, Class<T> clazz) {
    	exportDataByBatch(title, dir, dataIterator, null, clazz);
    }
    
    /**
     * 分批导出Excel
     * @param <T>
     * @param title Excel名称
     * @param dir 目录名称
     * @param dataIterator 数据迭代器
     * @param dataWriter 数据处理器
     * @param clazz 数据类
     */
    public static <T> void exportDataByBatch(String title, String dir, DataIterator<T> dataIterator, DataWriter<T> dataWriter, Class<T> clazz) {
    	
    	String tmpPath = getDefaultExportDir();
    	if(dataWriter == null) {
    		dataWriter = new DefaultDataWriter<>();
    	}
    	//准备数据
    	dataIterator.prepareData();
    	
    	String fileName = title + "批量导出-" + DateUtil.formatDate(new Date(), DateUtil.PATTERN_YYYYMMDDHHMMSS);
        
        List<String> pathList = new ArrayList<>();
        if(dataIterator.totalCount() > 0) {
        	ExcelWriter excelWriter = null;
    		try {
    			tmpPath = tmpPath.endsWith("/")?tmpPath:tmpPath+"/";
    	        tmpPath += dir;
    			tmpPath = tmpPath.endsWith("/")?tmpPath:tmpPath+"/";
    			FileUtil.mkDirs(tmpPath);
    			String fileNameFormat = tmpPath + fileName + "-%s.xlsx";
    			
    			int fileNo = 0;
    			WriteSheet writeSheet = null;
    			while(dataIterator.hasNext()) {
    				List<T> dataList = dataIterator.nextBatchData();
    				if(CollectionUtil.isEmpty(dataList)) {
    					continue;
    				}
    				if(fileNo != dataIterator.getFileNo()) {
    					fileNo = dataIterator.getFileNo();
    					String filePath = String.format(fileNameFormat, fileNo);
    					pathList.add(filePath);
    					//关闭上次的Excel文件
    					close(excelWriter);
    					excelWriter = dataWriter.createExcel(filePath, clazz);
    					writeSheet = dataWriter.createSheet(title, fileNo);
    				}
    				dataWriter.writeData(excelWriter, writeSheet, dataList);
    			}
    		} catch (Exception e) {
    			logger.error(title + "导出生成Excel异常", e);
                throw new BusinessException(title + "导出生成Excel异常");
    		} finally {
    			close(excelWriter);
    		}
        }

        //没有生成文件，此时返回一个空的Excel
		if (CollectionUtil.isEmpty(pathList)) {
			DataWriter<T> finalDataWriter = dataWriter;
			downloadFile(fileName + ".xlsx", out ->{
        		ExcelWriter excelWriter = null;
        		try {
        			excelWriter = finalDataWriter.createExcel(out, clazz);
            		finalDataWriter.writeData(excelWriter, finalDataWriter.createSheet(title, 0), Collections.emptyList());
				} finally {
					close(excelWriter);
				}
        	});
            return;
        }
		
		//导出的Excel只有一个文件，直接返回该Excel
        if (pathList.size() < 2) {
            downloadFile(fileName + ".xlsx", out -> {
                try (FileInputStream in = new FileInputStream(pathList.get(0))) {
                    FileUtil.copyLarge(in, out);
                } catch (Exception e) {
                    logger.error(title + "导出单个Excel异常", e);
                    throw new BusinessException(title + "导出单个Excel异常", e);
                }
            });
            return;
        }

        //生成了多个Excel文件，压缩然后再返回一个压缩包
        downloadFile(fileName + ".zip", out -> {
            try {
                ZipUtils.zipFile(out, pathList, "/");
            } catch (Exception e) {
                logger.error(title + "导出异常", e);
                throw new BusinessException(title + "导出异常", e);
            }
        });
    }
    
    private static void close(ExcelWriter excelWriter) {
    	if(excelWriter != null) {
    		excelWriter.finish();
    	}
	}
    
    
}
